/*
 * sr_dir.c - EnCluster Standard Representation Directory Type Operations
 * Clunix, glory@clunix.com, yjlee@clunix.com, 2001.6.5,19.
 * Clunix, cezanne@clunix.com, 2005.2
 *
 * Copyright (c) 2001-2005 by Clunix Inc. All Rights Reserved.
 */

#include "config.h"

#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <stdlib.h>

#include "sr.h"
#include "sr_p.h"

ECMBOOL
sr_exist(const sr_t *sr_group, const char *name)
{
	if (sr_getc(sr_group, name))
		return ECM_TRUE;
	return ECM_FALSE;
}

ECMBOOL
sr_mightbe_member(const sr_t *sr_group, const sr_t *sr_mbr)
{
	if (sr_group == NULL || sr_mbr == NULL)
		return ECM_FALSE;
	if (srt_is_member(sr_group->type, sr_mbr->type))
		return ECM_TRUE;
	return ECM_FALSE;
}

ECMBOOL
srt_is_group(srt_t srt)
{
	if (srt & SR_VHMS)
		return ECM_TRUE;
	if (srt & SR_VIRTUAL)
		return ECM_FALSE;
	if ((srt & SR_HOMOSET) && (srt & SR_REFHMS) == 0)
		return ECM_TRUE;
	if (srt & SR_REF)
		return ECM_FALSE;
	if (SR_BT(srt) == SR_BT_DIR)
		return ECM_TRUE;
	return ECM_FALSE;
}

ECMBOOL
sr_is_group(const sr_t *sr_group)
{
	if (sr_group == NULL)
		return ECM_FALSE;
	return srt_is_group(sr_group->type);
}

ECMBOOL
srt_is_HMS(srt_t srt)
{
	if (srt & SR_VHMS)
		return ECM_FALSE;
	if (srt & SR_VIRTUAL)
		return ECM_FALSE;
	if ((srt & SR_HOMOSET) && (srt & SR_REFHMS) == 0)
		return ECM_TRUE;
	return ECM_FALSE;
}

ECMBOOL
sr_is_HMS(const sr_t *sr)
{
	if (sr == NULL)
		return ECM_FALSE;
	return srt_is_HMS(sr->type);
}

ECMBOOL
srt_is_vHMS(srt_t srt)
{
	if (srt & SR_VHMS)
		return ECM_TRUE;
	return ECM_FALSE;
}

ECMBOOL
sr_is_vHMS(const sr_t *sr)
{
	if (sr == NULL)
		return ECM_FALSE;
	return srt_is_vHMS(sr->type);
}

ECMBOOL
srt_is_virtual(srt_t srt)
{
	if (srt & SR_VIRTUAL)
		return ECM_TRUE;
	return ECM_FALSE;
}

ECMBOOL
sr_is_virtual(const sr_t *sr)
{
	if (sr == NULL)
		return ECM_FALSE;
	return srt_is_virtual(sr->type);
}

ECMBOOL
srt_is_HMSgroup(srt_t srt)
{
	if (srt & SR_VHMS)
		return ECM_TRUE;
	if (srt & SR_VIRTUAL)
		return ECM_FALSE;
	if ((srt & SR_HOMOSET) && (srt & SR_REFHMS) == 0)
		return ECM_TRUE;
	return ECM_FALSE;
}

ECMBOOL
sr_is_HMSgroup(const sr_t *sr)
{
	if (sr == NULL)
		return ECM_FALSE;
	return srt_is_HMSgroup(sr->type);
}

ECMBOOL
srt_is_real(srt_t srt)
{
	if (srt & (SR_VIRTUAL | SR_VHMS))
		return ECM_FALSE;
	return ECM_TRUE;
}

ECMBOOL
sr_is_real(const sr_t *sr)
{
	if (sr == NULL)
		return ECM_FALSE;
	return srt_is_real(sr->type);
}

sr_t *
sr_get_root(const sr_t *sr)
{
	sr_t	*sr_parent;

	sr_parent = (sr_t *)sr;
	while (sr_parent && sr_parent->parent)
		sr_parent = sr_parent->parent;
	return sr_parent;
}

ECMBOOL
sr_is_root(const sr_t *sr)
{
	if (sr && sr->parent == NULL)
		return ECM_TRUE;
	return ECM_FALSE;
}

ECMBOOL
sr_is_descendant(const sr_t *sr, const sr_t *sr_descendant)
{
	while (sr_descendant) {
		if (sr_descendant->parent == sr)
			return ECM_TRUE;
		sr_descendant = sr_descendant->parent;
	}
	return ECM_FALSE;
}

sr_t *
sr_get_parent(const sr_t *sr)
{
	if (sr == NULL)
		return NULL;
	return sr->parent;
}

srt_t
sr_get_member_type(const sr_t *sr)
{
	if (sr == NULL)
		return ST_NULL;
	return srt_get_member_type(sr->type);
}

static ECMBOOL
is_mergeable(sr_t *sr_group, sr_t *sr_merged)
{
	srt_t	srt_mbr, srt_mbr_merged;

	if (!sr_is_group(sr_group) || !sr_is_group(sr_merged))
		return ECM_FALSE;
	srt_mbr = sr_get_member_type(sr_merged);
	if (srt_mbr == ST_NULL)
		return ECM_TRUE;
	srt_mbr_merged = sr_get_member_type(sr_merged);
	if (srt_mbr_merged == ST_NULL)
		return ECM_FALSE;
	return srt_is_member(sr_get_type(sr_group), srt_mbr_merged);
}

void
sr_merge(sr_t *sr_group, sr_t *sr_merged)
{
	struct list_head	*lp, *next;
	sr_t	*sr_mbr;

	if (!is_mergeable(sr_group, sr_merged))
		return;

	list_for_each_n(lp, &sr_merged->srv_head, next) {
		sr_mbr = list_entry(lp, sr_t, list);
		sr_crop(sr_mbr);
		if (sr_append(sr_group, sr_mbr) == 0)
			sr_free(sr_mbr);
	}
}

void
sr_merge_unique(sr_t *sr_group, sr_t *sr_merged)
{
	struct list_head	*lp, *next;
	sr_t	*sr_mbr;

	if (!is_mergeable(sr_group, sr_merged))
		return;

	list_for_each_n(lp, &sr_merged->srv_head, next) {
		sr_mbr = list_entry(lp, sr_t, list);
		sr_crop(sr_mbr);
		if (sr_append_unique(sr_group, sr_mbr) == 0)
			sr_free(sr_mbr);
	}
}

void
sr_merge_force(sr_t *sr_group, sr_t *sr_merged)
{
	struct list_head	*lp, *next;
	sr_t	*sr_mbr;

	if (!sr_is_group(sr_group) || !sr_is_group(sr_merged))
		return;

	list_for_each_n(lp, &sr_merged->srv_head, next) {
		sr_mbr = list_entry(lp, sr_t, list);
		sr_crop(sr_mbr);
		if (sr_append(sr_group, sr_mbr) == 0)
			sr_free(sr_mbr);
	}
}

sr_t *
sr_add_child(sr_t *sr_group, const char *name, sr_t *sr_child)
{
	if (sr_group == NULL || sr_child == NULL)
		return NULL;
	if (!sr_mightbe_member(sr_group, sr_child))
		return NULL;

	sr_set_name(sr_child, name);
	return sr_add_idx_force(sr_group, -1, sr_child);
}

sr_t *
sr_get_child(const sr_t *sr_group, const char *name)
{
	sr_t	*sr_child;
	struct list_head	*lp;

	if (!sr_is_group(sr_group))
		return NULL;

	list_for_each (lp, &sr_group->srv_head) {
		sr_child = list_entry(lp, sr_t, list);
		if (ecm_strcmp(sr_get_name(sr_child), name) == 0)
			return sr_child;
	}
	return NULL;
}

sr_t *
sr_get_sibling(const sr_t *sr, const char *name)
{
	sr_t	*sr_sib;
	struct list_head	*lp;
	const struct list_head	*lp_end;

	if (sr == NULL)
		return NULL;

	if (sr->parent)
		lp_end = &sr->parent->srv_head;
	else
		lp_end = &sr->list;

	for (lp = sr->list.next; lp != lp_end; lp = lp->next) {
		sr_sib = list_entry(lp, sr_t, list);
		if (ecm_strcmp(sr_get_name(sr_sib), name) == 0)
			return sr_sib;
	}
	return NULL;
}

static sr_t *
do_sr_let(sr_t *sr_old, sr_t *sr_new)
{
	struct list_head	*lp;
	sr_t	*sr_parent = sr_old->parent;

	lp = sr_old->list.prev;
	sr_free(sr_old);
	list_add(&sr_new->list, lp);
	sr_new->parent = sr_parent;

	return sr_new;
}

sr_t *
sr_set_child(sr_t *sr_group, const char *name, sr_t *sr_child)
{
	sr_t	*sr_old;

	if (sr_group == NULL || sr_child == NULL)
		return NULL;
	if (!sr_mightbe_member(sr_group, sr_child))
		return NULL;

	sr_set_name(sr_child, name);
	sr_old = sr_get_child(sr_group, name);
	if (sr_old == NULL)
		return sr_add_idx_force(sr_group, -1, sr_child);
	return do_sr_let(sr_old, sr_child);
}

void
sr_clear_child(sr_t *sr_group)
{
	sr_t	*sr_child, *sr_next;

 	SR_FOREACH_CHILD_N(sr_group, NULL, sr_child, sr_next)
 		sr_free(sr_child);
}

sr_t *
sr_getf(sr_t *dir, char *format, ...)
{
	va_list ap;
	sr_t *ret;
	char *buf;

	va_start(ap, format);
	ecm_vasprintf(&buf, format, ap);
	va_end(ap);

	ret = sr_getn(dir, buf);
	ecm_free(buf);
	return ret;
}

/* Directory operation with index; idx starts at 0 and end at child_num() - */

int 
sr_get_num_child(const sr_t *sr_group)
{
	int			n = 0;
	struct list_head	*lp;

	if (!sr_is_group(sr_group))
		return -1;

	list_for_each(lp, &sr_group->srv_head)
		n++;
	return n;
}

ECMBOOL
sr_is_empty_group(const sr_t *sr_group)
{
	if (sr_is_group(sr_group) && list_empty(&sr_group->srv_head))
		return ECM_TRUE;
	return ECM_FALSE;
}

sr_t *
sr_get_idx(const sr_t *sr_group, int idx)
{
	struct list_head	*head, *lp;

	if (!sr_is_group(sr_group))
		return NULL;

	head = &((sr_t *)sr_group)->srv_head;
	if (list_empty(head))
		return NULL;
	if (idx < 0)
		return list_entry(head->prev, sr_t, list);

	list_for_each(lp, head) {
		if (idx == 0)
			return list_entry(lp, sr_t, list);
		idx--;
	}
	return NULL;
}

sr_t *
sr_add_idx_force(sr_t *sr_group, int idx, sr_t *sr_mbr)
{
	if (idx < 0)
		list_add_tail(&sr_mbr->list, &sr_group->srv_head);
	else {
		sr_t	*sr_sib;
		sr_sib = sr_get_idx(sr_group, idx);
		if (sr_sib == NULL)
			list_add_tail(&sr_mbr->list, &sr_group->srv_head);
		else
			list_add_tail(&sr_mbr->list, &sr_sib->list);
	}
	sr_mbr->parent = sr_group;
	return sr_mbr;
}

sr_t *
sr_add_idx(sr_t *sr_group, int idx, sr_t *sr_mbr)
{
	if (sr_group == NULL || sr_mbr == NULL)
		return NULL;

	if (!sr_mightbe_member(sr_group, sr_mbr))
		return NULL;
	return sr_add_idx_force(sr_group, idx, sr_mbr);
}

sr_t *
sr_set_idx(sr_t *sr_group, int idx, sr_t *sr_child)
{
	sr_t	*sr_old;

	if (sr_group == NULL || sr_child == NULL)
		return NULL;
	if (!sr_mightbe_member(sr_group, sr_child))
		return NULL;

	sr_old = sr_get_idx(sr_group, idx);
	if (sr_old == NULL)
		return NULL;
	return do_sr_let(sr_old, sr_child);
}

static ECMBOOL
has_child(sr_t *sr_group, sr_t *sr_child)
{
	sr_t	*sr;
	const char	*name;

	name = sr_get_name(sr_child);
	SR_FOREACH_CHILD(sr_group, NULL, sr) {
		if (ecm_strcmp(sr_get_name(sr), name) == 0)
			return ECM_TRUE;
	}
	return ECM_FALSE;
}

sr_t *
sr_append_unique(sr_t *sr_group, sr_t *sr_mbr)
{
	if (has_child(sr_group, sr_mbr))
		return NULL;
	return sr_append(sr_group, sr_mbr);
}

/* Directory operation without name --------------------------------------- */

sr_t *
sr_get_next(const sr_t *sr)
{
	if (sr == NULL)
		return NULL;
	if (sr->parent) {
		if (sr->list.next == &sr->parent->srv_head)
			return NULL;
	}
	else {
		if (sr->list.next == &sr->list)
			return NULL;
	}
	return list_entry(sr->list.next, sr_t, list);
}

sr_t *
sr_get_prev(const sr_t *sr)
{
	if (sr == NULL)
		return NULL;
	if (sr->parent) {
		if (sr->list.prev == &sr->parent->srv_head)
			return NULL;
	}
	else {
		if (sr->list.prev == &sr->list)
			return NULL;
	}
	return list_entry(sr->list.prev, sr_t, list);
}

sr_t *
sr_upcast_vHMS(sr_t *sr_vHMS, srt_t srt)
{
	if (sr_vHMS == NULL)
		return NULL;
	SR_CHECK(sr_vHMS);

	if (srt_is_compatible(sr_get_type(sr_vHMS), srt)) {
		sr_vHMS->type = srt;
		return sr_vHMS;
	}
	return NULL;
}
